Analyse : Concevez une application au service de la santé publique

  • Projet-3 : Analyse des données
  • IDEE APPLICATION
  • 1. Chargement du jeu de données (dataset en anglais)
  • Analyse des dates de création et modification des produits
  • ANALYSE UNIVARIEE
    • Quantitatives continues
    • Variable quantitative discrete
      • 'nutrition-score-fr_100g'
    • Les variables qualitalives
      • Variables qualitatives nominales
      • Qui sont les sources de ces données ?
      • Les marques
      • Catégories
      • Les additifs à surveillées sont ils des informations dont nous pourrions disposer ?
        • Additifs ciblés
    • Variables qualitatives ordinales
      • Répartition des nutrition_grades
  • ANALYSE BIVARIEES
    • Les corrélations
    • Nutriscore / nutrigrade
        • Nutrigrade | Protéines
  • TARGET VARIABLES
        • TARGET : alimentation saine
        • VARIABLES : protéger le reins
  • ANOVA
  • ANALYSE MULTIVARIEE
  • Analyse en Composantes Principales (ACP)
  • Analyse en Composantes Principales (ACP)

Projet-3 : Analyse des données¶

In [1]:
import pandas as pd
import numpy as np
import datetime as dt
import sys
import warnings
import IPython as ip
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import seaborn as sns
import scipy.stats as st
from scipy.stats import t, shapiro
import statsmodels
import statsmodels.api as sm 
import statsmodels.formula.api as smf
from statsmodels.stats.outliers_influence import variance_inflation_factor
from statsmodels.graphics.gofplots import qqplot
# ACP
from sklearn.preprocessing import StandardScaler
from sklearn import decomposition
from sklearn.decomposition import PCA
from sklearn import decomposition
from sklearn import preprocessing


from IPython.display import display
import missingno as msno 
# Configuration pour travail avec fichier python "tools" de fonctions
%load_ext autoreload
%aimport tools
# Recharger les modules pour la conception des fichiers tools
%autoreload 1

# Set option
pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', None)


warnings.filterwarnings("ignore")

IDEE APPLICATION¶


  • L'application doit permettre aux personnes qui recherchent des conseils alimentaires en cas de maladie rénale chronique de trouver des informations suppleméntaires (info visuel) qui est spécifique au gens qui doivent préserver au mieux leurs reins (problème rénal).
  • L'application ne remplace pas un régime spécifique fait par un professionnel de santé mais constitue une aide à a décision par une information simplifiée concernant les indicateur qui sont importants de surveiller lorsqu'on doit surveiller son alimentation pour préserver ses reins.

  • Objectifs : COMMENT PROTEGER MES REINS ?
    • Limiter l’apport en sel
    • Contrôler les apports en protéines,
    • Couvrir les besoins nutritionnels,
    • Conserver le plaisir de manger.

On surveille :

  • Le poids : alimentation saine, équilibrée et plaisir :
    • 'nutrition-score-fr_100g','energy_100g'
    • 'fat_100g', 'saturated-fat_100g','carbohydrates_100g', 'sugars_100g', 'fiber_100g'
  • Les indicateurs disponibles :
    • Sel('salt_100g')
    • Sodium('sodium_100g') : sel minéral qui intervient dans l’équilibre hydrique du corps
      • 1g de sel (NaCl) équivaut à 400 mg de sodium (Na)
      • 1 gramme de sodium(Na) correspond donc à 2.5 g de sel
    • Proteines ('proteins_100g') : Au stade d’insuffisance rénale les besoins en protéines sont de 0,8g/Kg/j
      • en fonction du poids :
        • -60kg => 40g
        • 60kg => 46g
        • 70kg => 56g
        • 80kg => 64g
    • Le potassium est un minéral important présent dans un grand nombre d’aliments indispensables au bon fonctionnement des muscles et du coeur. : trop de données manquantes
    • Le phosphore est surtout présent dans les aliments, lié aux protéines.
      • La limitation protéique entraîne déjà une diminution des apports en phosphore.
    • Limiter les additifs :
      • E 338 Acide phosphorique (boisson au cola)
      • E 339 Phosphates de sodium
      • E 340 Phosphates de potassium
      • E 341 Phosphates de calcium
      • E 343 Phosphates de magnésium
      • E 450 Diphosphates
      • E 451 Triphosphates
      • E 452 Polyphosphates

Problématique : Les données du jeu de données peuvent-elles répondre aux objectifs ?


1. Chargement du jeu de données (dataset en anglais)¶

In [2]:
# Import données
data = pd.read_csv('assets/datas/df_app_knnImputer.csv', sep='\t',parse_dates=[2,3], low_memory=False)
In [3]:
df = data.copy()
# Visualisation d'un échantillon de la population
df.sample(5)
Out[3]:
code creator created_datetime last_modified_datetime product_name brands categories_fr countries_fr additives_n additives_fr ingredients_from_palm_oil_n nutrition_grade_fr main_category_fr energy_100g fat_100g saturated_fat_100g carbohydrates_100g sugars_100g fiber_100g proteins_100g salt_100g sodium_100g nutrition_score_fr_100g
191216 3452200002624 kiliweb 2017-03-23 11:57:27 2017-03-23 11:57:27 Confiture Myrtilles Confiturelle inconnu France 0.0 0.0 c inconnu 611.0 0.00 0.05 7.422 28.50 1.400 0.50 0.07000 0.027559 6.0
112611 0708820847019 usda-ndb-import 2017-03-09 12:29:41 2017-03-09 12:29:41 Bruschetta, Diced Tomatoes Meijer inconnu États-Unis 1.0 E330 - Acide citrique 0.0 b inconnu 134.0 0.79 0.00 6.350 4.76 1.600 1.59 0.52324 0.206000 1.0
236729 8694490000040 usda-ndb-import 2017-03-09 10:50:34 2017-03-09 10:50:34 Apricot Preserves Naturello inconnu États-Unis 2.0 E440 - Pectines,E330 - Acide citrique 0.0 0 inconnu 962.0 0.00 0.22 65.000 60.00 0.188 0.00 0.00000 0.000000 10.8
10243 0015400003650 usda-ndb-import 2017-03-09 12:56:05 2017-03-09 12:56:05 American Pasteurized Prepared Cheese Product Western Family inconnu États-Unis 6.0 E331 - Citrates de sodium,E341 - Phosphate de ... 0.0 e inconnu 1393.0 23.81 14.29 9.520 4.76 0.000 14.29 3.26644 1.286000 25.0
145551 0858290004044 usda-ndb-import 2017-03-09 16:38:19 2017-03-09 16:38:19 Ruuska All Naturals Pickles, Spicy Dill Pickles Ruuska All Natural Pickles Llc inconnu États-Unis 0.0 0.0 c inconnu 88.0 0.00 0.00 4.170 0.00 0.000 0.00 2.01168 0.792000 8.0
In [4]:
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 240304 entries, 0 to 240303
Data columns (total 23 columns):
 #   Column                       Non-Null Count   Dtype         
---  ------                       --------------   -----         
 0   code                         240304 non-null  object        
 1   creator                      240304 non-null  object        
 2   created_datetime             240304 non-null  datetime64[ns]
 3   last_modified_datetime       240304 non-null  datetime64[ns]
 4   product_name                 240304 non-null  object        
 5   brands                       240304 non-null  object        
 6   categories_fr                240304 non-null  object        
 7   countries_fr                 240304 non-null  object        
 8   additives_n                  240304 non-null  float64       
 9   additives_fr                 240304 non-null  object        
 10  ingredients_from_palm_oil_n  240304 non-null  float64       
 11  nutrition_grade_fr           240304 non-null  object        
 12  main_category_fr             240304 non-null  object        
 13  energy_100g                  240304 non-null  float64       
 14  fat_100g                     240304 non-null  float64       
 15  saturated_fat_100g           240304 non-null  float64       
 16  carbohydrates_100g           240304 non-null  float64       
 17  sugars_100g                  240304 non-null  float64       
 18  fiber_100g                   240304 non-null  float64       
 19  proteins_100g                240304 non-null  float64       
 20  salt_100g                    240304 non-null  float64       
 21  sodium_100g                  240304 non-null  float64       
 22  nutrition_score_fr_100g      240304 non-null  float64       
dtypes: datetime64[ns](2), float64(12), object(9)
memory usage: 42.2+ MB
In [5]:
tools.get_description_variables(df,type_var='categ')
Out[5]:
count unique top freq first last
code 240304 240304 0000000004530 1 NaT NaT
creator 240304 2477 usda-ndb-import 153975 NaT NaT
created_datetime 240304 126364 2017-03-09 10:37:09 19 2012-01-31 14:43:58 2017-04-20 21:13:06
last_modified_datetime 240304 119721 2015-08-09 17:35:48 22 2012-04-08 08:12:35 2017-04-21 00:53:41
product_name 240304 186889 Extra Virgin Olive Oil 192 NaT NaT
brands 240304 46265 inconnue 3145 NaT NaT
categories_fr 240304 16277 inconnu 178919 NaT NaT
countries_fr 240304 81 États-Unis 155671 NaT NaT
additives_fr 240304 38480 102639 NaT NaT
nutrition_grade_fr 240304 6 d 57653 NaT NaT
main_category_fr 240304 2322 inconnu 178919 NaT NaT
In [6]:
tools.get_description_variables(df,type_var='num')
Out[6]:
count mean std min 25% 50% 75% max
additives_n 240304.0 1.787436 2.464454 0.0 0.00000 1.00000 3.00000 31.000000
ingredients_from_palm_oil_n 240304.0 0.017582 0.133063 0.0 0.00000 0.00000 0.00000 2.000000
energy_100g 240304.0 1123.056095 786.012530 0.0 389.00000 1100.00000 1674.00000 3776.000000
fat_100g 240304.0 12.054291 16.700297 0.0 0.00000 4.60000 19.00000 100.000000
saturated_fat_100g 240304.0 4.614094 7.549919 0.0 0.00000 1.25000 6.67000 100.000000
carbohydrates_100g 240304.0 31.203502 28.476440 0.0 6.45000 20.00000 56.76000 100.000000
sugars_100g 240304.0 15.283422 20.688020 0.0 1.01000 5.10000 22.58000 100.000000
fiber_100g 240304.0 2.435193 4.211174 0.0 0.00000 1.02000 3.30000 100.000000
proteins_100g 240304.0 7.113039 8.106444 0.0 0.71000 4.88000 10.00000 100.000000
salt_100g 240304.0 1.592437 6.193162 0.0 0.06858 0.59182 1.37414 100.000000
sodium_100g 240304.0 0.626730 2.436071 0.0 0.02700 0.23300 0.54300 39.370079
nutrition_score_fr_100g 240304.0 8.959197 8.787331 -15.0 1.00000 9.00000 15.80000 40.000000

Analyse des dates de création et modification des produits¶

In [7]:
add_per_year = df['code'].groupby(by=df['created_datetime'].dt.year).nunique()
modified_per_year = df['code'].groupby(by=df['last_modified_datetime'].dt.year).nunique()

fig=plt.figure(figsize=(12,8))

font_title = {'family': 'serif',
              'color':  '#114b98',
              'weight': 'bold',
              'size': 18,
             }

sns.set_style("whitegrid")
plt.plot(add_per_year, 
         color="#114b98", 
         label="Ajouts")
plt.plot(modified_per_year, 
         color="#00afe6", 
         label="Modifications")
plt.title("Evolution des créations et modifications de produits par année", 
          fontdict=font_title)
plt.xlabel("Année")
plt.ylabel("Nombre de produits")
plt.legend()
plt.savefig("assets/graphiques/Evolutions_dates.jpg")

plt.show()
  • Quelle que soit les valeurs faisant référence aux dates : on observe un augmentation significative dans les années 2015-2017
  • created_t = created_datetime
  • last_modified-t = last_modified_datetime
  • created_t dispose d'information remontant à 1970 : 01/01/1970 est une erreur car c'est la seule et la date est antérieur au projet
  • Explication des dates :
    • Le Nutri-Score est prévu dans la loi de 2016 en France
    • Mis en place en France en 2017, l'étiquetage nutritionnel Nutri-Score s'applique aujourd'hui dans sept pays

ANALYSE UNIVARIEE¶

In [8]:
float_columns  = df.select_dtypes(include=['float64']).columns.to_list()
object_columns = df.select_dtypes(include=['object']).columns.to_list()
datetime_columns = df.select_dtypes(include=['datetime64[ns]']).columns.to_list()
In [9]:
# On écarte les dates
# Variables numériques
cols_num = df.select_dtypes(include=[np.number]).columns.to_list()
# Variables quantitatives discrètes
cols_quant_discr = ['additives_n','ingredients_from_palm_oil_n','nutriscore_score_fr']
# Variables quantitatives continue
cols_quant_cont = ['energy_100g', 'fat_100g','saturated_fat_100g', 
                   'carbohydrates_100g', 'sugars_100g', 'fiber_100g',
                   'proteins_100g', 'salt_100g', 'sodium_100g']

Quantitatives continues¶

In [10]:
# Mesures de tendances centrales des colonnes quantitatives continues
tools.stat_descriptives(df,cols_quant_cont)
Out[10]:
Desc energy_100g fat_100g saturated_fat_100g carbohydrates_100g sugars_100g fiber_100g proteins_100g salt_100g sodium_100g
mean 1123.056095 12.054291 4.614094 31.203502 15.283422 2.435193 7.113039 1.592437 0.626730
median 1100.000000 4.600000 1.250000 20.000000 5.100000 1.020000 4.880000 0.591820 0.233000
var 617813.126622 278.898761 57.001032 810.904287 427.992372 17.733913 65.714153 38.355100 5.934415
std 786.010895 16.700262 7.549903 28.476381 20.687977 4.211165 8.106427 6.193149 2.436065
skew 0.425871 2.231646 3.502017 0.623817 1.725949 5.409043 2.126997 11.073758 11.093051
kurtosis -0.446573 6.514148 22.325536 -0.954933 2.479089 58.608099 8.571377 142.319727 142.787027
mode 0 0.0 0 0.0 0 0.0 0 0.0 0 0.0 0 0.0 0 0.0 0 0.0 0 0.0
Min 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
Max 3776.000000 100.000000 100.000000 100.000000 100.000000 100.000000 100.000000 100.000000 39.370079

Si γ1=0 alors la distribution est symétrique.

Si γ1>0 alors la distribution est étalée à droite.

Si γ1<0 alors la distribution est étalée à gauche.

In [11]:
def var_hist(var, i):
    subset = df[var]
    n_df_valide = len(df)
    xbar = np.mean(df[var]) # Moyenne
    sprime = np.std(df[var], ddof=1) # Ecart-type
    sprime2 = np.var(df[var], ddof=1) #Variance non biaisée
    
    ax = fig.add_subplot(i)
    ax.hist(subset, density=True)
    ax.axvline(xbar, color='r', linewidth=2, label="Moyenne empirique")
    bins = np.arange(df[var].min(),df[var].max(),0.05)
    y = st.norm.pdf(bins, xbar, sprime)
    ax.plot(bins, y, '--', label="Densité normale")
    ax.legend()
    ax.set_xlabel(var, fontsize=12)
    ax.set_ylabel('Densité', fontsize=12)
    ax.set_title('Distribution de '+str(var), fontsize=18)
In [12]:
liste_var = cols_quant_cont
plt.style.use('seaborn-whitegrid')
fig = plt.figure(figsize=(20,30),constrained_layout=False)
i = 331
for var in liste_var :
    var_hist(var, i)
    i+=1
plt.savefig("assets/graphiques/analyse univariee histo_dfComplet.jpg")
In [13]:
# Representation graphique des outliers:
a = 3  # nombre de lignes
b = 3  # nombre de colonnes
c = 1  # initialisation
fig = plt.figure(figsize=(20,8))

for i in df.loc[:, cols_quant_cont]: # pour toute les colonnnes quantatives
    plt.subplot(a, b, c) # maillage des subplot
    plt.title('{} (boxplot)'.format(i, a, b, c))# titres des box plot
    plt.xlabel(i) # xlabel = nom de la colonne
    sns.boxplot(x = df[i]) # faire un boxplot sns
    c = c + 1 # incrementation ==> création d'un nouveau box plot
plt.subplots_adjust(left=0.125, # gerer les espacements
                    bottom=0.1, 
                    right=0.9, 
                    top=0.9, 
                    wspace=0.2, 
                    hspace=0.35)

ANALYSE :

Variable quantitative discrete¶

'nutrition-score-fr_100g'¶

In [14]:
# On s'occupe ici uniquement des nutrigrades complétés
df_nutri = df[~(df['nutrition_grade_fr']=='0')]
# On s'occupe ici uniquement des nutrigrades complétés
df_nutriscore = df[~(df['nutrition_score_fr_100g']=='0')]
df_nutriscore = df_nutriscore[~(df_nutriscore['nutrition_grade_fr']=='0')] 
In [15]:
# Courbe de distribution du nutriscore
plt.figure(figsize=(12, 8))

sns.histplot(df_nutriscore['nutrition_score_fr_100g'], kde=True,
             color='SteelBlue', label='Nutri_score pour 100g de produit')
plt.title("Distribution du nutri-score", fontsize=14)
plt.xlim(-15, 40)
plt.xlabel('Score', fontsize=12)
plt.ylabel('Nombre de produits par score', fontsize=12)
plt.legend()
plt.show()
In [16]:
fig = plt.figure(figsize=(15, 6))

ax1 = fig.add_subplot(1, 2, 1)
box = sns.boxplot(data=df_nutri['nutrition_score_fr_100g'], color='SteelBlue', ax=ax1)
# box.set(ylabel=unite)

plt.grid(False)

ax2 = fig.add_subplot(1, 2, 2)
ax2 = sm.qqplot(df_nutri['nutrition_score_fr_100g'],
             line='r', ax=ax2)
plt.grid(False)

fig.suptitle('Dispersion des nutrition-score-fr_100g', fontweight='bold', size=14)
plt.show()
In [17]:
col = ['nutrition_score_fr_100g']
tools.stat_descriptives(df_nutri,col)
Out[17]:
Desc nutrition_score_fr_100g
mean 9.131191
median 10.000000
var 81.995724
std 9.055149
skew 0.116733
kurtosis -1.017174
mode 0 0.0
Min -15.000000
Max 40.000000
In [18]:
# définition des bacs
# https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.IntervalIndex.from_tuples.html
liste_bins = pd.IntervalIndex.from_tuples(
    [(-15, -1), (0, 2), (3, 10), (10, 18), (19, 40)])
tools.distribution_variables_plages_perc_donnees(df_nutri,'nutrition_score_fr_100g',liste_bins)
Out[18]:
Plage nb_données %_données
(-15, -1] 33037 16.241980
(0, 2] 20121 9.892087
(3, 10] 35405 17.406160
(10, 18] 58204 28.614832
(19, 40] 32121 15.791647

Bilan A COMPLETER


  • La distribution est , ce qui correspond .
  • La distribution semble non normale surtout pour les produits de tête et de fin.
  • La moyenne et la médiane sont autour d'un score de .
  • L'amplitude est de -15 à 40 donc tous les nutri-scores sont représentés.
  • Le nutri-score est majoritaire et la majorité des produits d'Open Food Facts ont

Les variables qualitalives¶

In [19]:
# On visualise le nombre de valeurs uniques contenu dans les colonnes de type object
for col in df.select_dtypes('object'):
    print(f'{col:-<50} {df[col].nunique()}')
code---------------------------------------------- 240304
creator------------------------------------------- 2477
product_name-------------------------------------- 186889
brands-------------------------------------------- 46265
categories_fr------------------------------------- 16277
countries_fr-------------------------------------- 81
additives_fr-------------------------------------- 38480
nutrition_grade_fr-------------------------------- 6
main_category_fr---------------------------------- 2322

Variables qualitatives nominales¶

In [20]:
# Variables qualitatives ou modalités
# Variables qualitatives nominales
cols_qual_nom = ['code','creator','product_name','brands', 
                 'categories_fr','main_category_fr', 'countries_fr','additives_fr']
In [21]:
def top_N_pie (df,var,name,n,taille,perc) : 
    ''' 
    Fonction qui visualise les n plus grand d'une colonne avec ou sans pourcentage
    parametres : 
        df
        var : colonne ciblée
        name : 'nom de la colonne '
        n : nombre de top voulu
        taille : taille du pieplot
        per : si True : affiche les pourcentages
    '''
    target = df.groupby(by=var)['code'].nunique().sort_values(ascending=False)
    # Graphiques top N
    
    fig, ax = plt.subplots(figsize=(taille, taille), subplot_kw=dict(aspect="equal"))
    explodes = np.zeros(n)
    explodes[0] = .1
    # calcul des pourcentages
    if perc: 
        def pct_tot(pct):
            tot = round(pct*target[:n].sum(),0)
            tot_pct = tot/target.sum()
            return "{:.1f}%\n({:.0f})".format(tot_pct,(tot/100))
        plt.pie(target[:n], labels=target[:n].index,
            startangle=45,
            shadow=True,
            autopct=lambda pct: pct_tot(pct),
            explode=explodes,
            textprops=dict(color="black",size=12, weight="bold"))
    else : 
        plt.pie(target[:n], labels=target[:n].index,
                startangle=45,
                shadow=True,
                explode=explodes,
                textprops=dict(color="black",size=10, weight="bold"))
    plt.title(f"TOP {n} : {name}",fontweight='bold',fontsize=24)
    plt.show()

Qui sont les sources de ces données ?¶

In [22]:
# Nombre de créateurs, sources des données
print(f"Nombre de sources unique : {df['creator'].nunique()}")
Nombre de sources unique : 2477
In [23]:
top_N_pie(df,'creator','Contributeurs',5,12,True)
plt.savefig("assets/graphiques/Top_Contributeurs.jpg")
<Figure size 640x480 with 0 Axes>
  • 2478 contributeurs dont des institutions internationales mais aussi des particuliers
  • La question de la fiabilité des données se pose dès les 6 plus gros contributeurs
    • La majorité des données provient de l'USDA : Misitère de l'agriculture américain : https://www.usda.gov/topics/trade/importing-goods
    • openfoodfacts-contributors : Communauté de contributeurs : https://world.openfoodfacts.org/contribute
    • kiliweb : ??
    • openfood-ch-import : sûrement une instition suisse mais pas de certitudes
    • date-limie-app : ??
    • Tacite : contributeur pernonnel ?

Les marques¶

  • Trouve t-on dans le jeu de données les marques qui sont présentent sur le marché français, consommateurs cible de notre application ?
In [24]:
df['brands'].nunique()
Out[24]:
46265
In [25]:
# On s'occupe ici uniquement des catégories renseignées
df_brands = df[~(df['brands']=='inconnue')]
df_brands.shape
Out[25]:
(237159, 23)
In [26]:
# Tableau fréquences
dico = df_brands.groupby('brands')['brands'].count().sort_values(ascending=False).to_dict()
nom = 'brands'
col1 = 'Nom_' + nom
col2 = 'Nbr_' + nom
col3 = 'Fréquence (%)'
df_gpe = pd.DataFrame(dico.items(), columns=[col1, col2])
df_gpe[col3] = (df_gpe[col2] * 100) / len(df_brands)
df_gpe.head(10)
Out[26]:
Nom_brands Nbr_brands Fréquence (%)
0 Carrefour 2362 0.995956
1 Auchan 1768 0.745491
2 Meijer 1702 0.717662
3 U 1681 0.708807
4 Kroger 1454 0.613091
5 Leader Price 1372 0.578515
6 Casino 1233 0.519904
7 Ahold 1181 0.497978
8 Roundy's 1112 0.468884
9 Spartan 1063 0.448223
In [27]:
# Wordecloud
from wordcloud import WordCloud

wordcloud = WordCloud(width=800,height=400, background_color="white",max_words=100).generate_from_frequencies(dico)
plt.figure(figsize=(12, 10))
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis("off")
plt.show()
  • Sur 46265 marques différentes dans le df
    • 237159 produit non renseignés : 'inconnues'
  • Les marques disponibles en France sont bien représentées :
  • Carrefour, Auchan, Leader Price, U, Casino, Monoprix, Picard, Bjorg, Nestlé, Lipton, Cora, Harris... sont des marques populaires en France
In [28]:
 df_gp_red = df_gpe.head(10)
sns.set_style("whitegrid")
plt.figure(figsize=(8, 4))
sns.barplot(
    y=df_gp_red[col1],
    x=df_gp_red[col3],
    data=df_gp_red,
    color='SteelBlue')
plt.title('Répartition de la présence des marques dans le jeu de données')
plt.grid(False)
plt.tight_layout()
plt.show()

Catégories¶

In [29]:
df['categories_fr'].nunique()
Out[29]:
16277
In [30]:
# On s'occupe ici uniquement des catégories renseignées
df_categ = df[~(df['categories_fr']=='inconnu')]
In [31]:
tools.affiche_wordcloud_tabfreq(df_categ,'categories_fr','categories',)
Nom_categories Nbr_categories Fréquence (%)
Snacks sucrés,Biscuits et gâteaux,Biscuits 691 1.125682
Snacks sucrés,Chocolats,Chocolats noirs 529 0.861774
Aliments et boissons à base de végétaux,Aliments d'origine végétale,Petit-déjeuners,Céréales et pommes de terre,Céréales et dérivés,Céréales pour petit-déjeuner 450 0.733078
Snacks sucrés,Biscuits et gâteaux,Biscuits,Biscuits au chocolat 409 0.666287
Snacks salés,Apéritif,Biscuits apéritifs 404 0.658141
Snacks sucrés,Confiseries,Bonbons 374 0.609269
Snacks sucrés,Chocolats,Chocolats au lait 363 0.591350
Produits laitiers,Yaourts 349 0.568543
Snacks sucrés,Chocolats 301 0.490348
Epicerie,Sauces 299 0.487090
In [32]:
tools.affiche_wordcloud_tabfreq(df_categ,'main_category_fr','Main categories',affword=False)
Nom_Main categories Nbr_Main categories Fréquence (%)
Boissons 2290 3.730553
Epicerie 2278 3.711004
Aliments et boissons à base de végétaux 2274 3.704488
Chocolats 2252 3.668649
Conserves 2014 3.280932
Biscuits 1853 3.018653
Plats préparés 1813 2.953490
Surgelés 1713 2.790584
Petit-déjeuners 1566 2.551112
Snacks sucrés 1515 2.468030
  • Parmi les catégories les plus représentés ont retrouve beaucoup de produits considérés comme a surveiller dans une alimentation saine.
  • A défaut d'une sur représentation des produits sain on peut exploiter ces informationspour informer le consommateur sur les produits à surveiller. Une bonne alimentation passe aussi par le plaisir et ne doit pas être stigmatisé sans avis médical personnalisé contraire.

  • Pour notre appli cette source de données est importantes et l'application devra signaler les choses de manière pédagogique

    • Alimentation : interdite par le medecin
    • alimentation : apport à surveiller
    • alimentation : recommandé

Les additifs à surveillées sont ils des informations dont nous pourrions disposer ?¶


Additifs ciblés¶


  • Limiter les additifs :
     - E 338 Acide phosphorique (boisson au cola)
     - E 339 Phosphates de sodium
     - E 340 Phosphates de potassium
     - E 341 Phosphates de calcium
     - E 343 Phosphates de magnésium
     - E 450 Diphosphates
     - E 451 Triphosphates
     - E 452 Polyphosphates
In [33]:
# On s'occupe ici uniquement des catégories renseignées
df_additives = df[~(df['additives_fr']==' ')]
In [34]:
df_additives_target = df_additives.copy()
df_additives_target = df_additives_target[df_additives_target['additives_fr'].str.contains("338|339|340|341|343|450|451|452")]
In [35]:
tools.affiche_wordcloud_tabfreq(df_additives_target,'additives_fr','Additives',affword=False)
Nom_Additives Nbr_Additives Fréquence (%)
E452vi - Tripolyphosphate de sodium et de potassium 345 1.564271
E339iii - Phosphate de sodium tribasique,E316 - Erythorbate de sodium,E250 - Nitrite de sodium 290 1.314895
E339iii - Phosphate de sodium tribasique 286 1.296758
E339 - Orthophosphates de sodium 211 0.956699
E339 - Orthophosphates de sodium,E316 - Erythorbate de sodium,E250 - Nitrite de sodium 194 0.879619
E450 - Sels métalliques de diphosphates 178 0.807073
E325 - Lactate de sodium,E339 - Orthophosphates de sodium,E262ii,E316 - Erythorbate de sodium,E250 - Nitrite de sodium 177 0.802539
E325 - Lactate de sodium,E339iii - Phosphate de sodium tribasique,E262ii,E316 - Erythorbate de sodium,E250 - Nitrite de sodium 175 0.793471
E375 - Acide nicotinique,E101 - Riboflavine,E450 - Sels métalliques de diphosphates 159 0.720925
E341iii - Phosphate de tricalcium 132 0.598504
Les addfitifs que nous ciblons sont dans la base de données
  • Les addfitifs que nous ciblons sont dans la base de données

Variables qualitatives ordinales¶

In [36]:
# Variables qualitatives ordinales
cols_qual_ord = ['nutriscore_grade_fr']

Répartition des nutrition_grades¶

In [37]:
# On s'occupe ici uniquement des nutrigrades complétés
df_nutri = df[~(df['nutrition_grade_fr']=='0')]
In [38]:
nutrition_grade = df_nutri.groupby(by='nutrition_grade_fr')['code'].nunique().sort_values(ascending=False)

fig, ax = plt.subplots(figsize=(6, 6), subplot_kw=dict(aspect="equal"))
explodes = np.zeros(5)
explodes[0] = .1

plt.pie(nutrition_grade, labels=nutrition_grade.index, 
        startangle=0, 
        colors=['#ee8100','#fecb02','#e63e11','#038141','#85bb2f'],
        shadow=True,
        explode=explodes,
        autopct='%1.1f%%',
        textprops=dict(color="black",size=12, weight="bold"))
plt.title("Répartition des Nutrition_grade", fontdict=font_title)
plt.savefig("assets/graphiques/Répartion_nutrigrdes.jpg")
plt.show()

Tous les nutrigrades sont représentés avec beaucoup de produits appartenant aus groupes 'd'et 'e' représentant 47% des produits de la base

Définition : que signifie le Nutri-Score ?

Conçu dans le cadre du Programme National Nutrition Santé, le Nutri-Score est une échelle graphique qui classe de A à E les produits alimentaires en fonction de leurs qualités nutritionnelles. Le système retenu se base ainsi sur un code à 5 couleurs : du vert pour les produits équilibrés, du rouge pour les aliments trop gras ou trop sucrés et trois couleurs intermédiaires (vert clair, jaune et orange).

► Les aliments classés A sont les plus favorables sur le plan nutritionnel car il s'agit de nutriments et d'aliments à favoriser (fibres, protéines, fruits, légumes, légumineuses, fruits à coques, huile de colza, de noix et d'olive),

► Les aliments classés E ont une moins bonne qualité nutritionnelle car ils contiennent des nutriments à limiter (énergie, acides gras saturés, sucres, sel).

Il s'agit de l'étiquetage nutritionnel officiel recommandé en France. Mis au point par des équipes de recherches internationales, synthétique, compréhensible et fondé sur des bases scientifiques, ce logo fournit une information immédiate au consommateur sur la qualité nutritionnelle des produits qu'il achète afin de l'aider à faire facilement les bons choix dans les rayons des supermarchés. 

ANALYSE BIVARIEES¶

Les corrélations¶

In [39]:
sns.pairplot(df_nutri.sample(frac=0.05), hue="nutrition_grade_fr")
plt.savefig("assets/graphiques/Pairplot_Nutrition grade.jpg")
In [40]:
sns.clustermap(df_nutri.corr(),annot=True)
Out[40]:
<seaborn.matrix.ClusterGrid at 0x11d2013b9a0>

Bilan


  • On observe des correlations entre le nutrigrade et les autres variables interressantes pour l'application
  • Certaines variables sont très corrélées :
    • Cette matrice des corrélations ne nous apporte pas rééllement d'informations mais confirme mathématiquement des éléments logiques : salt_100g est très fortement corrélé avec sodium_100g, fat_100g avec satured-fat_100g... Il faudra cependant tenir compte de ces fortes corrélation dans nos modèles, la colinéarité dégradant les performances.

Nutriscore / nutrigrade¶

In [41]:
# On s'occupe ici uniquement des nutrigrades complétés
df_nutriscore = df[~(df['nutrition_score_fr_100g']=='0')]
df_nutriscore = df_nutriscore[~(df_nutriscore['nutrition_grade_fr']=='0')] 
In [42]:
# graph
sns.histplot(data=df_nutriscore.sort_values("nutrition_grade_fr"), x="nutrition_score_fr_100g", hue="nutrition_grade_fr")
plt.show()
In [43]:
fig, axes = plt.subplots(1, 2, sharex=False, sharey=False, figsize=(21,8))
fig.suptitle(r"Répartition des scores Nutriscore et de leurs grades" "\n", fontsize=22)

sns.histplot(data=df_nutriscore.sort_values("nutrition_grade_fr"), x="nutrition_grade_fr", hue="nutrition_grade_fr", ax=axes[0])
axes[0].set_title('Grades de Nutriscores')
axes[0].set_xlabel("nutrition_grade_fr")
axes[0].set_ylabel("Nombre de produits")

sns.histplot(data=df_nutriscore.sort_values("nutrition_grade_fr"), x="nutrition_score_fr_100g", hue="nutrition_grade_fr", ax=axes[1])
axes[1].set_title('Scores de Nutriscores')
axes[1].set_xlabel("Score Nutriscore")
axes[1].set_ylabel("Nombre de produits")

plt.show()
  • nutrigrade :
    • La répartition du nombre de produits en fonction de leur appartenance a un groupe Grade de Nutriscore est :
      • Le groupe de produit d est le plus nombreux
        • Les produits considérés comme a surveillés sont bien représenté (d et e)
      • Les 5 groupes sont présents
    • Concernant les scores entre -15 et 40 :
      • Les scores les plus représentés sont en entre 0 et 5
      • Pic autour des 15 (grade d)
      • Les grade sont bien représentatifs des scores et sont plutôt diversifiés
In [44]:
# Répartition des variables quantitative en fonction du nutrigrade
colors_nutri = ['#038141','#85bb2f','#fecb02','#ee8100','#e63e11']
fig = plt.figure(figsize=(20, 35))

for i, c in enumerate(df.select_dtypes('float'), 1):
    ax = fig.add_subplot(6, 2, i)
    sns.boxplot(data=df_nutri, x='nutrition_grade_fr', y=c,order='abcde', ax=ax,palette=colors_nutri)
    plt.grid(False)
    plt.xticks(fontsize=16)
    plt.yticks(fontsize=16)

plt.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.suptitle('Répartition des variables quantitatives en fonction du nutrigrade', fontsize=30)
plt.savefig("assets/graphiques/Répartition des variables quantitatives en fonction du nutrigrade.jpg")
plt.show()
  • Répartition générale :

    • Les données sont généralement asymétriques, la majorité d'entre elles sont situées sur le côté supérieur ou inférieur du graphique et présentent de nombreux outliers. L'asymétrie indique que les données peuvent ne pas être normalement distribuées.
      • Beaucoup de valeurs atypiques quelque soit le groupe
        • la diversité des produits explique ces résultats
  • Energie : Plus un produit apporte de l'énegie plus il faut surveiller les quantités (classes CDE)

  • fat, satured-fat : Plus un produit
    • Centres : AB peu gras, CDE : plus gras, à surveiller
    • Dispersion : difference de dispersion CDE
  • Sucres :
    • Centres : Plus un produit contient de sucre plus son nutrigrade augmente
    • Dispersion : beaucoup d'outliers dans les AB mais l'apport de sucre est limité 80g pourt 100g
  • Protéines :
    • Centres (comparer les médianes) : les médianes sont proches entre les groupes
    • Dispersion : Très marquées par les outliers
  • Sel (sodium et sel): Plus un produit contient de sel plus il est à surveiller
    • Centres (comparer les médianes) : Les produits à éviter et à surveiller font parti de ABC
    • Dispersion : beaucoup de produit très salé appartiennent aux groupes considérés comme à maîtriser : C,D,E
  • Nutri-score : conforme à la séparation nutrigraded (assez normal).

  • On regarde les proteines importantes pour notre application :

    • Hypothèse plus un produit est considéré comme sain (A, B) moins il contient de protéines

Nutrigrade | Protéines¶

In [45]:
# Préparation des variables de travail pour les graphiques et les tests
gp = df_nutri.groupby('nutrition_grade_fr')['proteins_100g']
df_nutrigrade = pd.DataFrame([gp.get_group(n).values for n in list('abcde')],
                             index=list('abcde')).T
df_a = df_nutrigrade['a']
df_b = df_nutrigrade['b']
df_c = df_nutrigrade['c']
df_d = df_nutrigrade['d']
df_e = df_nutrigrade['e']
In [46]:
plt.figure(figsize=[10, 10])
# colors_nutri = ['#038141','#85bb2f','#fecb02','#ee8100','#e63e11']
# Boxplot protéines/nutri-score grade
plt.subplot(2, 1, 1)
sns.boxplot(data=df_nutri, x='nutrition_grade_fr', y='proteins_100g',
            palette=colors_nutri, order='abcde')
plt.ylim(0, 100)
plt.ylabel('Nombre de g de protéines pour 100g de produit', fontsize=12)
plt.xlabel('Nutri-grade_fr', fontsize=12)
plt.title('Protéines par nutri-grade', fontsize=14)
plt.grid(False)
# Ajout moyenne des protéines pour tous les produits
moyenne_proteines = df_nutri['proteins_100g'].mean()
plt.axhline(y=moyenne_proteines, color='r')

# Violinplot protéines/nutri-score grade
plt.subplot(2, 1, 2)
sns.violinplot(data=df_nutri, x='nutrition_grade_fr', y='proteins_100g',
               palette=colors_nutri, order='abcde')
plt.ylabel('Nombre de g de protéines pour 100g de produit', fontsize=12)
plt.xlabel('Nutri-grade', fontsize=12)
plt.grid(False)
# Ajout moyenne des protéines pour tous les produits
plt.axhline(y=moyenne_proteines, color='r')

plt.show()
  • amplitude en 0 et 100g de protéines
  • pas de correlation entre nutrigrade et nombre de protéine pour 100g
In [47]:
# Distplot protéines
sns.distplot(df_nutri['proteins_100g'], bins=100, color='SteelBlue')
plt.grid(False)
In [48]:
qqplot(df_nutri['proteins_100g'], line='r')
plt.grid(False)

plt.show()
  • amplitude en 0 et 100g de protéines
  • pas de correlation entre nutrigrade et nombre de protéine pour 100g
  • La distribution ne semble pas normal ==> ANOVA pour confirmer
In [49]:
# Statistiques descriptives
tools.stat_descriptives(df_nutri, ['proteins_100g'])
Out[49]:
Desc proteins_100g
mean 7.782852
median 5.710000
var 64.586628
std 8.036581
skew 2.011552
kurtosis 7.844765
mode 0 0.0
Min 0.000000
Max 100.000000
In [50]:
df_nutrigrade.head()
Out[50]:
a b c d e
0 16.67 17.86 14.06 3.57 5.0
1 11.76 14.55 10.91 17.86 7.5
2 10.91 14.29 8.89 16.67 2.5
3 12.73 12.96 10.71 7.50 7.5
4 11.76 10.91 10.91 13.33 5.0
In [51]:
import matplotlib.patches as mpatches

fig = plt.figure(figsize=(8, 6))
label_patches = []

sns.kdeplot(df_nutri['proteins_100g'], color='Blue')
label_patch = mpatches.Patch(
    color='Blue',
    label='Ensemble des nutrigrades')
label_patches.append(label_patch)
plt.grid(False)
plt.xlim([-1, 40])

i = 1
for n, c in zip(list('abcde'), colors_nutri):
    i += 1
    sns.kdeplot(df_nutrigrade[n], color=c)
    label_patch = mpatches.Patch(color=c, label=n)
    label_patches.append(label_patch)
    plt.grid(False)
    plt.xlim([-1, 40])

fig.suptitle('Distribution des protéines', fontweight='bold', fontsize=14)
plt.legend(handles=label_patches,bbox_to_anchor=(1.05,1),loc=2,borderaxespad=0., facecolor='white')
plt.tight_layout()
plt.grid(False)
plt.show()
In [52]:
liste_bins = pd.IntervalIndex.from_tuples(
    [(0, 4), (5, 9), (10, 14), (15, 19), (20, 24), (25, 29),
     (30, 34), (35, 39), (40, 100)])
tools.distribution_variables_plages_perc_donnees(df_nutri, 'proteins_100g', liste_bins)
Out[52]:
Plage nb_données %_données
(0, 4] 57852 28.441779
(5, 9] 44923 22.085494
(10, 14] 19937 9.801627
(15, 19] 9812 4.823874
(20, 24] 8187 4.024975
(25, 29] 2984 1.467024
(30, 34] 985 0.484256
(35, 39] 409 0.201077
(40, 100] 850 0.417885
In [53]:
df_nutri['proteins_100g'].describe()
Out[53]:
count    203405.000000
mean          7.782852
std           8.036600
min           0.000000
25%           2.000000
50%           5.710000
75%          10.710000
max         100.000000
Name: proteins_100g, dtype: float64
  • amplitude en 0 et 100g de protéines
  • pas de correlation entre nutrigrade et nombre de protéine pour 100g
  • On rertrouve dans toutes les plages toutes les notes du nutrtion grade
  • 75% des variables contiennent >10g de proteine CCL : Rappel de l'hypothèse : plus un produit est considéré comme sain (A, B) moins il contient de protéines
  • notre hypothèse est fause ==> test ANOVA
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 

ANOVA¶

In [54]:
import statsmodels.formula.api as smf
import statsmodels.api as sm

anova_nutrigrade = smf.ols('salt_100g~nutrition_grade_fr', data=df_nutri).fit()
print(anova_nutrigrade.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:              salt_100g   R-squared:                       0.020
Model:                            OLS   Adj. R-squared:                  0.020
Method:                 Least Squares   F-statistic:                     1053.
Date:                Mon, 09 Jan 2023   Prob (F-statistic):               0.00
Time:                        16:06:32   Log-Likelihood:            -5.6608e+05
No. Observations:              203405   AIC:                         1.132e+06
Df Residuals:                  203400   BIC:                         1.132e+06
Df Model:                           4                                         
Covariance Type:            nonrobust                                         
===========================================================================================
                              coef    std err          t      P>|t|      [0.025      0.975]
-------------------------------------------------------------------------------------------
Intercept                   0.3317      0.022     15.395      0.000       0.289       0.374
nutrition_grade_fr[T.b]     0.1973      0.031      6.394      0.000       0.137       0.258
nutrition_grade_fr[T.c]     1.3756      0.029     47.811      0.000       1.319       1.432
nutrition_grade_fr[T.d]     1.3139      0.027     48.641      0.000       1.261       1.367
nutrition_grade_fr[T.e]     1.1733      0.029     40.160      0.000       1.116       1.231
==============================================================================
Omnibus:                   390897.003   Durbin-Watson:                   1.250
Prob(Omnibus):                  0.000   Jarque-Bera (JB):        753180834.671
Skew:                          15.237   Prob(JB):                         0.00
Kurtosis:                     299.547   Cond. No.                         6.46
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
In [55]:
import statsmodels.formula.api as smf
import statsmodels.api as sm

anova_nutrigrade = smf.ols('proteins_100g~nutrition_grade_fr', data=df_nutri).fit()
print(anova_nutrigrade.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:          proteins_100g   R-squared:                       0.028
Model:                            OLS   Adj. R-squared:                  0.028
Method:                 Least Squares   F-statistic:                     1491.
Date:                Mon, 09 Jan 2023   Prob (F-statistic):               0.00
Time:                        16:06:33   Log-Likelihood:            -7.0958e+05
No. Observations:              203405   AIC:                         1.419e+06
Df Residuals:                  203400   BIC:                         1.419e+06
Df Model:                           4                                         
Covariance Type:            nonrobust                                         
===========================================================================================
                              coef    std err          t      P>|t|      [0.025      0.975]
-------------------------------------------------------------------------------------------
Intercept                   8.3360      0.044    191.083      0.000       8.250       8.421
nutrition_grade_fr[T.b]    -3.2269      0.062    -51.651      0.000      -3.349      -3.104
nutrition_grade_fr[T.c]    -1.3744      0.058    -23.593      0.000      -1.489      -1.260
nutrition_grade_fr[T.d]     0.2438      0.055      4.457      0.000       0.137       0.351
nutrition_grade_fr[T.e]     0.8275      0.059     13.988      0.000       0.712       0.943
==============================================================================
Omnibus:                    91895.107   Durbin-Watson:                   0.937
Prob(Omnibus):                  0.000   Jarque-Bera (JB):           713257.449
Skew:                           2.014   Prob(JB):                         0.00
Kurtosis:                      11.242   Cond. No.                         6.46
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
In [56]:
import statsmodels.formula.api as smf
import statsmodels.api as sm

anova_nutrigrade = smf.ols('energy_100g~nutrition_grade_fr', data=df_nutri).fit()
print(anova_nutrigrade.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:            energy_100g   R-squared:                       0.376
Model:                            OLS   Adj. R-squared:                  0.376
Method:                 Least Squares   F-statistic:                 3.070e+04
Date:                Mon, 09 Jan 2023   Prob (F-statistic):               0.00
Time:                        16:06:34   Log-Likelihood:            -1.5885e+06
No. Observations:              203405   AIC:                         3.177e+06
Df Residuals:                  203400   BIC:                         3.177e+06
Df Model:                           4                                         
Covariance Type:            nonrobust                                         
===========================================================================================
                              coef    std err          t      P>|t|      [0.025      0.975]
-------------------------------------------------------------------------------------------
Intercept                 725.1980      3.284    220.833      0.000     718.762     731.634
nutrition_grade_fr[T.b]  -204.7798      4.703    -43.543      0.000    -213.997    -195.562
nutrition_grade_fr[T.c]   272.1357      4.385     62.057      0.000     263.541     280.731
nutrition_grade_fr[T.d]   746.4478      4.117    181.299      0.000     738.378     754.517
nutrition_grade_fr[T.e]  1110.2484      4.453    249.329      0.000    1101.521    1118.976
==============================================================================
Omnibus:                     8953.183   Durbin-Watson:                   0.939
Prob(Omnibus):                  0.000   Jarque-Bera (JB):            10833.178
Skew:                           0.487   Prob(JB):                         0.00
Kurtosis:                       3.574   Cond. No.                         6.46
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
In [57]:
sm.stats.anova_lm(anova_nutrigrade, typ=2)
Out[57]:
sum_sq df F PR(>F)
nutrition_grade_fr 4.366278e+10 4.0 30699.743948 0.0
Residual 7.232152e+10 203400.0 NaN NaN
In [ ]:
 
In [ ]:
 

ANALYSE MULTIVARIEE¶

Analyse en Composantes Principales (ACP)¶

In [58]:
df_acp = df_nutri.copy()
df_acp = df_acp.drop(columns=['additives_n','ingredients_from_palm_oil_n'])
# df_nutri=df_acp.copy() 
In [59]:
# Données expression
X = df_acp .select_dtypes('number') 
print('X', X.shape)
X (203405, 10)
In [60]:
# Etiquettes correspondantes (sous-types moléculaires)
y = df_acp ['nutrition_grade_fr']
print('y', y.shape)
y (203405,)
In [61]:
sort_by_mean = X.mean().sort_values(ascending=True)
X[sort_by_mean.index].plot(kind='box', figsize=(15, 5), rot=90, ylabel='Expression')
Out[61]:
<AxesSubplot: ylabel='Expression'>
In [62]:
scaler = StandardScaler() # instanciation de l'objet scaler
X_scaled = scaler.fit_transform(X) # normalisation centrée-réduite
X_scaled = pd.DataFrame(X_scaled, index=X.index, columns=X.columns) # conversion du résultat en objet dataframe de pandas
In [63]:
X_scaled.plot(kind='box', figsize=(15, 4), rot=90, ylabel='Expression')
Out[63]:
<AxesSubplot: ylabel='Expression'>
In [64]:
# Calcul de l'ACP
pca = PCA() # instanciation de l'objet pca
X_pca = pca.fit_transform(X_scaled) # réalisation de l'ACP sur les données X_scaled
In [65]:
# Conversion en dataframe pandas
pca_columns = ['PC' + str(c) for c in range(1, X_pca.shape[1]+1, 1)] # création d'une liste avec les noms de colonnes de PC1 à PC50
X_pca = pd.DataFrame(X_pca, index=X.index, columns=pca_columns) # création du dataframe
X_pca.head()
Out[65]:
PC1 PC2 PC3 PC4 PC5 PC6 PC7 PC8 PC9 PC10
0 2.976183 -0.443672 -0.429658 -0.430514 1.164263 0.067646 2.041363 0.465917 0.111609 -0.000113
1 0.482444 -0.433707 -0.053045 2.170992 -0.550786 -0.658820 -0.160754 0.701215 -0.032162 -0.000161
2 2.380716 0.684614 -1.944545 1.243508 0.489103 -1.004062 -1.416242 0.309401 0.060203 -0.000051
6 0.994814 -0.531088 -0.153626 1.946883 0.066815 -0.053333 0.043166 0.030669 0.062318 -0.000033
10 1.697466 0.513297 -1.071891 1.337685 0.060372 -0.741211 -0.559919 -0.079997 -0.029690 -0.000033
In [66]:
pca.explained_variance_ratio_
Out[66]:
array([3.16486435e-01, 2.08782529e-01, 1.73469763e-01, 1.32790701e-01,
       6.75125315e-02, 4.39407177e-02, 3.03336992e-02, 1.89560606e-02,
       7.72681459e-03, 7.48314104e-07])
In [67]:
# On peut les convertir en objet Series de pandas et présenter les valeurs en pourcentage.
explained_variance = pd.Series(dict(zip(X_pca.columns, 100.0*pca.explained_variance_ratio_)))
print(explained_variance.head())
PC1    31.648644
PC2    20.878253
PC3    17.346976
PC4    13.279070
PC5     6.751253
dtype: float64
In [68]:
explained_variance.plot(kind='bar', figsize=(15, 4), rot=90, ylabel='Explained variance')
Out[68]:
<AxesSubplot: ylabel='Explained variance'>
In [69]:
explained_variance['PC1'] + explained_variance['PC2']
Out[69]:
52.52689639260595
In [70]:
explained_variance['PC1'] + explained_variance['PC2'] + explained_variance['PC3']
Out[70]:
69.8738727385225
In [71]:
# Eboulis des valeurs propres

fig = plt.figure(figsize=(8,6))
plt.bar(np.arange(len(explained_variance))+1, explained_variance)
plt.plot(np.arange(len(explained_variance))+1, explained_variance.cumsum(),c="red",marker='o')
plt.xlabel("rang de l'axe d'inertie")
plt.ylabel("pourcentage d'inertie")
plt.title("Eboulis des valeurs propres")
plt.show(block=False)
#fig.savefig('img/ACP_eboulis_valeurs-propres.png')
In [72]:
X_pca.plot(x='PC1', y='PC2', kind='scatter', figsize=(5, 5), color='gray')
Out[72]:
<AxesSubplot: xlabel='PC1', ylabel='PC2'>
In [73]:
n_comp = len(df_acp.columns)
n = df_acp.shape[0]
p = df_acp.shape[1]
features =cols_acp
eigval= (n-1)/n*pca.explained_variance_
eigval
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [73], line 4
      2 n = df_acp.shape[0]
      3 p = df_acp.shape[1]
----> 4 features =cols_acp
      5 eigval= (n-1)/n*pca.explained_variance_
      6 eigval

NameError: name 'cols_acp' is not defined
In [ ]:
#Racine carrée des valeurs propres
sqrt_eigval = np.sqrt(eigval)
#Corrélation des variables avec les axes
covar = np.zeros((p,p))
for k in range(p):
    covar[:,k] = pca.components_[k,:] * sqrt_eigval[k]

mat_cor = pd.DataFrame(np.around(covar, 2),
                       index=features,
                       columns=['COR_'+str(i + 1) for i in range(p)])
mat_cor
In [ ]:
#Cercle des corrélations
tools.display_circles(covar.T, n_comp, pca,
                      [(0,1), (2,3), (4,5)],
                      labels = features)
plt.show()
In [ ]:
#Variable Illustrative
ivNutrigrade = df_nutri['nutrition_grade_fr'].values

#Encodage des nutrition_grades
from sklearn.preprocessing import LabelEncoder
encoder = LabelEncoder()
ivNutrigrade = encoder.fit_transform(ivNutrigrade)
ivNutrigrade = ivNutrigrade.reshape((ivNutrigrade.shape[0],1))

#Corrélation de la variable illustrative avec les axes factoriels 
corrIv = np.zeros((ivNutrigrade.shape[1],p))
for j in range(p): 
    for k in range(ivNutrigrade.shape[1]): 
        corrIv[k,j] = np.corrcoef(ivNutrigrade[:,k],X_projected[:,j])[0,1]
In [ ]:
# Representation du nuage des individus sur le premier plan factoriel
X_projected = pca.transform(X_scaled)

tools.display_factorial_planes(X_projected,
                         n_comp,
                         pca,
                         [(0,1), (2,3), (4,5)],
                         labels=None,
                         alpha=0.15,
                         illustrative_var=y)
plt.show()
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 

Analyse en Composantes Principales (ACP)¶

In [ ]:
df_nutri.columns
In [ ]:
 
In [ ]:
df_acp = df_nutri.copy()
df_acp = df_acp.drop(columns=['additives_n','ingredients_from_palm_oil_n'])
# df_nutri=df_acp.copy() 
In [ ]:
df_acp.head()
In [ ]:
# Sélection des colonnes pour l'ACP
cols_acp = df_acp.select_dtypes(include=[np.number]).columns.to_list()
# Nombre de composantes
n_comp = len(cols_acp)
# Données pour l'ACP
df_acp = df[cols_acp]
# Noms affichés
names = df_nutri['product_name']
features = df_acp.columns
X = df_acp.values
In [ ]:
# Centrage et Réduction - Mise à l'échelle
std_scaler = preprocessing.StandardScaler().fit(X)
X_scaled = std_scaler.transform(X)
In [ ]:
# Calcul des composantes principales
pca = decomposition.PCA(n_components=n_comp)
pca.fit(X_scaled)
In [ ]:
# Valeurs propres ou variances des composantes principales
val_propres = pca.explained_variance_
val_propres
In [ ]:
df_acp = pd.DataFrame(pca.components_,
                      index=['PC'+str(i+1) for i in range(n_comp)],
                      columns=cols_acp).T
df_acp
In [ ]:
# Distribution des composantes principales de l'ACP
C = pca.transform(X_scaled)
plt.figure(figsize=(15,4))
plt.boxplot(C)
plt.title('Distribution des composantes principales')
plt.grid(False)
plt.show()
In [ ]:
# quel est le pourcentage de variance préservée par chacune de
# nos composantes?
variances = pca.explained_variance_ratio_
variances
In [ ]:
# quelle est la somme cumulée des variances?
somme_cumule_var = np.cumsum(variances)
somme_cumule_var
In [ ]:
# Quel est le nombre minimum de composantes principales pour expliquer 95% de la variance
plt.plot(somme_cumule_var)
# argmax pour > 95 %
top = np.argmax(somme_cumule_var > 0.95)
plt.axhline(y=0.95, color='r')
plt.text(2, 0.96, '>95%', color='r', fontsize=10)
plt.axvline(x=top, color='r')
plt.title('Taux cumulé de variances expliquées pour les composantes')
plt.xlabel('Nombre de composantes')
plt.ylabel('Taux cumulé des variances')
plt.show()

A partir de la 6ème composante on explique 95% de la variance On peut réduire notre jeu de données.

In [ ]:
# Eboulis des valeurs propres
tools.display_scree_plot(pca)
  • Coude leger à 4 (methode du choix du nombre de composante)
  • Les composantes principales 11 et 12 n'explique aucune variance
  • Les 3 premières composantes expliquent : 70% de la variances
    • Les 3 premiers plans factoriels couvrent une inertie d'un peu plus de 70%.
In [ ]:
n_comp = len(df_acp.columns)
n = df_acp.shape[0]
p = df_acp.shape[1]
features =cols_acp
eigval= (n-1)/n*pca.explained_variance_
eigval
In [ ]:
#Racine carrée des valeurs propres
sqrt_eigval = np.sqrt(eigval)
#Corrélation des variables avec les axes
covar = np.zeros((p,p))
for k in range(p):
    covar[:,k] = pca.components_[k,:] * sqrt_eigval[k]

mat_cor = pd.DataFrame(np.around(covar, 2),
                       index=features,
                       columns=['COR_'+str(i + 1) for i in range(p)])
mat_cor
In [ ]:
 
In [ ]:
#Cercle des corrélations
tools.display_circles(covar.T, n_comp, pca,
                      [(0,1), (2,3), (4,5)],
                      labels = features
                     )
plt.show()
In [ ]:
# Representation du nuage des individus sur le premier plan factoriel
# echantillon
X_ech_index = df_acp.loc[:, cols_acp].sample(300).index
X_ech = df_acp.loc[X_ech_index, cols_acp].values
X_proj_ech = preprocessing.StandardScaler().fit_transform(X_ech)

tools.display_factorial_planes(X_proj_ech,
                               n_comp,
                               pca,
                               [(0,1), (2,3)],
                               labels=None,
                               alpha=1,
                               illustrative_var=df_nutri.loc[X_ech_index,'nutrition_grade_fr'])
In [ ]:
 
In [ ]:
 
In [ ]:
 

Kaggle

In [ ]:
df_acp = df_nutri.copy()
df_acp = df_acp.drop(columns=['additives_n','ingredients_from_palm_oil_n'])
# df_nutri=df_acp.copy() 
In [ ]:
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
In [ ]:
#Centrage et réduction
X = df_acp.select_dtypes(include=['float64'])
X_scaled = scaler.fit_transform(X)

#Instanciation de l'ACP
pca = PCA(svd_solver='full').fit(X_scaled)
X_projected = pca.transform(X_scaled)
In [ ]:
#Variances expliquées en % d'inertie
varexpl = pca.explained_variance_ratio_*100

#Projection de l'éboulis des valeurs propres
plt.figure(figsize=(8,4))
plt.bar(np.arange(len(varexpl))+1, varexpl)
plt.plot(np.arange(len(varexpl))+1, varexpl.cumsum(),c="red",marker='o')
plt.xlabel("rang de l'axe d'inertie")
plt.ylabel("pourcentage d'inertie")
plt.title("Eboulis des valeurs propres", fontdict=font_title)
plt.show(block=False)
In [ ]:
print("Le premier plan factoriel couvrira une inertie de {:.2f}% et le second plan : {:.2f}%.".format(varexpl[0:2].sum(),
                                                                                                     varexpl[0:4].sum()))
In [ ]:
#Espace des composantes principales
pcs = pca.components_

#Matrice des corrélations variables x facteurs
p = X.shape[1]
sqrt_valprop = np.sqrt(pca.explained_variance_)
corvar = np.zeros((p, p))
for dim in range(p):
    corvar[:,dim] = pcs[dim,:] * sqrt_valprop[dim]

#on affiche pour les deux premiers plans factoriels 
corr_matrix = pd.DataFrame({'feature':X.columns,'CORR_F1':corvar[:,0],'CORR_F2':corvar[:,1], 
              'CORR_F3':corvar[:,2], 'CORR_F4':corvar[:,3]})
corr_matrix
In [ ]:
#Variable Illustrative
ivNutrigrade = df_nutri['nutrition_grade_fr'].values

#Encodage des nutrition_grades
from sklearn.preprocessing import LabelEncoder
encoder = LabelEncoder()
ivNutrigrade = encoder.fit_transform(ivNutrigrade)
ivNutrigrade = ivNutrigrade.reshape((ivNutrigrade.shape[0],1))

#Corrélation de la variable illustrative avec les axes factoriels 
corrIv = np.zeros((ivNutrigrade.shape[1],p))
for j in range(p): 
    for k in range(ivNutrigrade.shape[1]): 
        corrIv[k,j] = np.corrcoef(ivNutrigrade[:,k],X_projected[:,j])[0,1]
In [ ]:
def cerle_corr(pcs, n_comp, pca, axis_ranks, 
               labels=None, label_rotation=0, 
               illustrative_var_label=None, illustrative_var_corr=None):
    for d1, d2 in axis_ranks:
        if d2 < n_comp:
            
            # initialisation de la figure
            fig=plt.figure(figsize=(10,10))
            fig.subplots_adjust(left=0.1,right=0.9,bottom=0.1,top=0.9)
            ax=fig.add_subplot(111)
            ax.set_aspect('equal', adjustable='box') 

            #détermination des limites du graphique
            ax.set_xlim(-1,1) 
            ax.set_ylim(-1,1) 

            #affichage des flèches 
            plt.quiver(np.zeros(pcs.shape[1]), np.zeros(pcs.shape[1]),
                       pcs[d1,:],pcs[d2,:], 
                       angles='xy', scale_units='xy', scale=1, 
                       color="grey", alpha=0.5)
            # et noms de variables
            for i,(x,y) in enumerate(pcs[[d1,d2]].T):
                plt.annotate(labels[i],(x,y),
                             ha='center', va='center',
                             fontsize='14',color="#17aafa", alpha=0.8) 

            #variable illustrative
            if illustrative_var_label is not None :
                plt.annotate(illustrative_var_label,
                             (illustrative_var_corr[0,d1],illustrative_var_corr[0,d2]),
                             color='g')
                plt.quiver(np.zeros(pcs.shape[1]), np.zeros(pcs.shape[1]),
                                   illustrative_var_corr[0,d1],illustrative_var_corr[0,d2], 
                                   angles='xy', scale_units='xy', scale=1, color="g", alpha=0.5)

            #ajouter les axes 
            plt.plot([-1,1],[0,0],linewidth=1, color='grey', ls='--') 
            plt.plot([0,0],[-1,1],linewidth=1, color='grey', ls='--')

            #ajouter un cercle 
            cercle = plt.Circle((0,0),1,color='#17aafa',fill=False) 
            ax.add_artist(cercle) 

            # nom des axes, avec le pourcentage d'inertie expliqué
            plt.xlabel('F{} ({}%)'.format(d1+1, round(100*pca.explained_variance_ratio_[d1],1)))
            plt.ylabel('F{} ({}%)'.format(d2+1, round(100*pca.explained_variance_ratio_[d2],1)))

            plt.title("Cercle des corrélations (F{} et F{})".format(d1+1, d2+1), fontdict=font_title)
            plt.show(block=False)
In [ ]:
cerle_corr(pcs, 4, pca, [(0,1),(2,3)], labels = np.array(X.columns), 
           illustrative_var_label="Nutrition_grade_fr", illustrative_var_corr = corrIv)
In [ ]:
 
In [ ]: